<?php
/** UTF-8
 * Concentré Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Concentre
 * @package    Concentre_Archive
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version	   $ID$ r$Rev: 16 $ $Author: alban.lopez $ $Date: 2009-03-28 12:42:00 +0000 (Sat, 28 Mar 2009) $
 * @author	   S�bastien Cramattte <scramatte@zensoluciones.com>
 * @author	   Alban Lopez <alban.lopez+archive@gmail.com>
 * @see		   Based on EasyArchive <http://www.phpclasses.org/browse/package/4239.html>
 */


/**
 * @see Concentre_Archive_Abstract
 */
require_once 'Concentre/Archive/Abstract.php';

/**
 * @see Concentre_Archive_Interface
 */
require_once 'Concentre/Archive/Interface.php';


/**
 * @category   Concentre
 * @package    Concentre_Archive
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Concentre_Archive_Tar extends Concentre_Archive_Abstract
{

    /**
     * Constructor
     *
     * @throws Concentre_Archive_Exception
     * @return void
     */
	public function __construct() {	
	}

    /**
     * Make archive
     *   
     * @param  string $src
     * @param  string $dest
     * @return binary|string|boolean
     */
	public function make($src, $dest=null)
	{
	
		if ( !file_exists($src) && !is_readable($src) ) {
			require_once 'Concentre/Archive/Exception.php';
			throw new Concentre_Archive_Exception(sprintf("Invalid source directory : %s", $src));
		}

		$tar = '';
		
		/*		
		$src = is_array($src) ? $src : array($src);
		foreach ($src as $item) {
			$tar .= $this->addItem($item.((is_dir($item) && substr($item, -1)!='/')?'/':''), dirname($item).'/');
		}
			
		foreach ($src as $item) {
			$tar .= $this->addItem(
			
			if ($item->isDir()) {
			
			}�else {
			
			
			}
			
		}
		
		*/
		
		$tar = str_pad($tar, floor((strlen($tar) + 10240 - 1) / 10240) * 10240, "\0");
				
		if (empty($dest)) {
			return $tar;
		} elseif (file_put_contents($dest, $tar)) {
			return $dest;
		}
		
		return false;
	}


    /**
     * Extract archive
     *   
     * @param  string $src
     * @param  string $dest
     * @return binary|string|boolean
     */	
	public function extract($src, $dest) 
	{
		if ($this->isTar($src))
		{
			$tmp= tempnam('/tmp','Concentre_');
			file_put_contents ($tmp, $src);
			
			$src = $tmp;
		}

		if ( !file_exists($dest) && !is_dir($dest) && !is_writable($dest) ) {
			require_once 'Concentre/Archive/Exception.php';
			throw new Concentre_Archive_Exception(sprintf("Invalid destination directory : %s", $dest));
		}

	    $dest = rtrim($dest, '\\/');
		
		$ptr = fopen($src, 'r');
		$result = array();
		
		while (!feof($ptr))
		{
			$infos = $this->readHeader ($ptr);
			$name = $dest.DIRECTORY_SEPARATOR.$infos['name'];
			
			if ($infos['type']=='5' && @mkdir($name, 0775, true)) {
				array_push($result, $name);
			} elseif (($infos['type']=='0' || $infos['type']==chr(0)) && file_put_contents($name, $infos['data']))  {
				array_push($result, $name);
			}
		}
		
		if (is_file($tmp)) {
			unlink($tmp);
		}
		
		return $result;
	}
	
    /**
     * Create 512 Header 
     *   
     * @param  
     * @return string;
     */
	protected function header512($infos)
	{ /* http://www.mkssoftware.com/docs/man4/tar.4.asp */
		$bigheader = $header = '';
		
		if (strlen($infos['name100'])>100) {
			$bigheader = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12",
				'././@LongLink','0000000','0000000','0000000',
				sprintf("%011o", strlen($infos['name100'])),'00000000000',
				'        ', 'L', '', 'ustar ', '0',
				$infos['userName32'],
				$infos['groupName32'],'','','','');

			$bigheader .= str_pad($infos['name100'], floor((strlen($infos['name100']) + 512 - 1) / 512) * 512, "\0");
			$checksum = 0;
			
			for ($i = 0; $i < 512; $i++) {
				$checksum += ord(substr($bigheader, $i, 1));
			}
			
			$bigheader = substr_replace($bigheader, sprintf("%06o", $checksum)."\0 ", 148, 8);
		}
		
		$header = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12", // book the memorie area
		
			substr($infos['name100'],0,100),		//  0 	100 	File name
			str_pad(substr(sprintf("%07o",$infos['mode8']),-4), 7, '0', STR_PAD_LEFT),		// 100 	8 		File mode
			sprintf("%07o", $infos['uid8']),		// 108 	8 		Owner user ID
			sprintf("%07o", $infos['gid8']),		// 116 	8 		Group user ID
			sprintf("%011o", $infos['size12']),		// 124 	12 		File size in bytes
			sprintf("%011o", $infos['mtime12']),	// 136 	12 		Last modification time
			'        ',								// 148 	8 		Check sum for header block
			$infos['link1'],						// 156 	1 		Link indicator / ustar Type flag
			$infos['link100'],						// 157 	100 	Name of linked file
			'ustar ',								// 257 	6 		USTAR indicator "ustar"
			' ',									// 263 	2 		USTAR version "00"
			$infos['userName32'],				// 265 	32 		Owner user name
			$infos['groupName32'],				// 297 	32 		Owner group name
			'',									// 329 	8 		Device major number
			'',									// 337 	8 		Device minor number
			$infos['prefix155'],					// 345 	155 	Filename prefix
			'');									// 500 	12 		??

		$checksum = 0;
		
		for ($i = 0; $i < 512; $i++) {
			$checksum += ord(substr($header, $i, 1));
		}
		
		$header = substr_replace($header, sprintf("%06o", $checksum)."\0 ", 148, 8);

		return $bigheader.$header;
	}

    /**
     * Add item to archive
     *   
     * @param  string $src
     * @param  string $dest
     * @return binary|string|boolean
     */	
	public function addItem ($item, $racine)
	{
		$infos['name100'] = str_replace($racine, '', $item);
		list (, , $infos['mode8'], , $infos['uid8'], $infos['gid8'], , , , $infos['mtime12'] ) = stat($item);
		$infos['size12'] = is_dir($item) ? 0 : filesize($item);
		$infos['link1'] = is_link($item) ? 2 : is_dir ($item) ? 5 : 0;
		$infos['link100'] == 2 ? readlink($item) : "";

			$a=function_exists('posix_getpwuid')?posix_getpwuid (fileowner($item)):array('name'=>'Unknown');
		$infos['userName32'] = $a['name'];

			$a=function_exists('posix_getgrgid')?posix_getgrgid (filegroup($item)):array('name'=>'Unknown');
		$infos['groupName32'] = $a['name'];
		$infos['prefix155'] = '';

		$header = $this->header512($infos);
		$data = str_pad(file_get_contents($item), floor(($infos['size12'] + 512 - 1) / 512) * 512, "\0");
		if (is_dir($item))
		{
			$lst = scandir($item);
			array_shift($lst); // remove  ./  of $lst
			array_shift($lst); // remove ../  of $lst
			foreach ($lst as $subitem)
				$sub .= $this->addTarItem($item.$subitem.(is_dir($item.$subitem)?'/':''), $racine);
		}
		return $header.$data.$sub;
	}
	

    /**
     * Read archive header
     *   
     * @param  string $ptr
     * @return binary|string|boolean
     */	
	protected function readHeader ($ptr)
	{
		$block = fread($ptr, 512);
		
		if (strlen($block)!=512) { 
			return false;
		}
		
		$hdr = unpack ("a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix/a12temp", $block);
		
		$hdr['mode']=$hdr['mode']+0;
		$hdr['uid']=octdec($hdr['uid']);
		$hdr['gid']=octdec($hdr['gid']);
		$hdr['size']=octdec($hdr['size']);
		$hdr['mtime']=octdec($hdr['mtime']);
		$hdr['checksum']=octdec($hdr['checksum']);
		
		
		$checksum = 0;
		$block = substr_replace($block, '        ', 148, 8);
		
		for ($i = 0; $i < 512; $i++) {
			$checksum += ord(substr($block, $i, 1));
		}
				
		if (isset($hdr['name']) && $hdr['checksum']==$checksum) {
			if ($hdr['name']=='././@LongLink' && $hdr['type']=='L') {
				$realName = substr(fread($ptr, floor(($hdr['size'] + 512 - 1) / 512) * 512), 0, $hdr['size']-1);
				$hdr2 = $this->readHeader ($ptr);
				$hdr2['name'] = $realName;
				return $hdr2;
			} elseif (strtolower(substr($hdr['magic'], 0, 5) == 'ustar')) {
				if ($hdr['size']>0)
					$hdr['data'] = substr(fread($ptr, floor(($hdr['size'] + 512 - 1) / 512) * 512), 0, $hdr['size']);
				else $hdr['data'] = '';
				return $hdr;
			}
			else {
				return false;
			}
		}
		
		return false;
	}
	
    /**
     * Get infos
     * 
     * @param  string $src
     * @return object
     */
	public function infos($src)
	{
		
		$ptr = fopen($src, 'r');
		$result = array();
			 
		while (!feof($ptr))
		{
			$infos = $this->readHeader ($ptr);
			unset($infos['data']);
			
			if ($infos['name'])
			{
				$result[ $infos['name'] ]= $this->_toObject($infos);
			}
		}
		
		return $result;
	}	
}
